/************************************************************************
* mapgen.c
* voxelands - 3d voxel world sandbox game
* Copyright (C) Lisa 'darkrose' Milne 2016 <lisa@ltmnet.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
************************************************************************/

#include "common.h"
#include "thread.h"
#include "list.h"

#define _MAPGEN_LOCAL
#include "map.h"

typedef struct mg_pending_s {
	struct mg_pending_s *prev;
	struct mg_pending_s *next;
	pos_t pos;
} mg_pending_t;

static struct {
	mutex_t *mutex;
	thread_t *thread;
	int state;
	mg_pending_t *pending;
	int pending_count;
	int pending_max;
} mapgen_data = {
	NULL,
	NULL,
	VLSTATE_EXIT,
	NULL,
	0,
	100
};

/* should return non-zero if position is outside the map */
static int region_pos(pos_t *p, pos_t *r)
{
	pos_t cp;

	cp.x = p->x;
	if (cp.x < 0)
		cp.x -= 16;
	cp.y = p->y;
	if (cp.y < 0)
		cp.y -= 16;
	cp.z = p->z;
	if (cp.z < 0)
		cp.z -= 16;

	r->x = (cp.x/32)*32;
	r->y = (cp.y/32)*32;
	r->z = (cp.z/32)*32;

	/* the 'ground zone' always 0 */
	if (r->y > -80 && r->y < 208)
		r->y = 0;

	return 0;
}

/* prepare and generate a region */
static void mapgen_process(pos_t *p)
{
	if (p->y < 0) {
		/* underground */
		mapgen_underground(p);
	}else if (p->y > 1023) {
		/* space */
		mapgen_space(p);
	}else if (p->y > 191) {
		/* high air */
		mapgen_air(p);
	}else{
		/* ground */
		mapgen_terrain(p);
	}
}

/* mapgen's main() */
static void *mapgen_thread(void* data)
{
	mg_pending_t *p;

	while (mapgen_data.state != VLSTATE_EXIT) {
		mutex_lock(mapgen_data.mutex);

		p = list_pull(&mapgen_data.pending);

		mutex_unlock(mapgen_data.mutex);

		if (p) {
			mapgen_process(&p->pos);
			free(p);
		}else{
			delay(10);
		}
	}

	while ((p = list_pop(&mapgen_data.pending))) {
		free(p);
	}

	mapgen_data.state = VLSTATE_EXIT;

	return NULL;
}

/* for config */
int mapgen_queue_max_setter(char* v)
{
	if (!v) {
		mapgen_data.pending_max = 100;
		return 0;
	}

	mapgen_data.pending_max = strtol(v,NULL,10);

	if (mapgen_data.pending_max > 0)
		return 0;

	mapgen_data.pending_max = 100;

	return 0;
}

/* initialise mapgen, start thread */
int mapgen_init()
{
	if (!mapgen_data.mutex)
		mapgen_data.mutex = mutex_create();

	mapgen_data.state = VLSTATE_PLAY;

	mapgen_data.thread = thread_create(mapgen_thread,NULL);

	return 0;
}

/* stop thread, wait for it */
void mapgen_exit()
{
	if (mapgen_data.state == VLSTATE_EXIT)
		return;
	mapgen_data.state = VLSTATE_EXIT;
	thread_wait(mapgen_data.thread);
}

/* request that a chunk at a given position be generated */
void mapgen_request(pos_t *p)
{
	pos_t r;
	mg_pending_t *pp;

	if (mapgen_data.pending_count >= mapgen_data.pending_max)
		return;

	if (region_pos(p,&r))
		return;

	mutex_lock(mapgen_data.mutex);

	pp = mapgen_data.pending;
	while (pp) {
		if (pp->pos.x == r.x && pp->pos.y == r.y && pp->pos.z == r.z) {
			mutex_unlock(mapgen_data.mutex);
			return;
		}
		pp = pp->next;
	}

	pp = malloc(sizeof(mg_pending_t));
	if (!pp)
		return;

	pp->pos.x = r.x;
	pp->pos.y = r.y;
	pp->pos.z = r.z;

	mapgen_data.pending = list_push(&mapgen_data.pending,pp);

	mutex_unlock(mapgen_data.mutex);
}
